package qc.module.platform.api.controller;

import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import qc.common.core.constants.QCConstant;
import qc.common.core.exception.QCPromptException;
import qc.common.core.utils.IdentifierUtil;
import qc.module.platform.api.FileUploadInputDto;
import qc.module.platform.dto.file.FileInfoDto;
import qc.module.platform.dto.file.FileQueryConditionDto;
import qc.module.platform.dto.file.FileUploadResultDto;
import qc.module.platform.entity.QcFiles;
import qc.module.platform.service.FileService;

import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * FileController
 *
 * @author QuCheng Tech
 * @since 2023/6/4
 */
@RestController
@RefreshScope
@RequestMapping("/file")
public class FileController {
    private FileService fileService;

    //@Autowired
    //HttpServletResponse response;

    @Autowired
    public void setFileService(FileService fileService) {
        this.fileService = fileService;
    }

    /**
     * 允许上传的文件扩展名
     * qucheng.upload.extension 自定义扩展名
     * 默认值：jpg.png.pdf.txt.doc.docx.xls.xlsx.ppt.pptx.mp4.rar.zip
     */
    @Value("#{'${qucheng.upload.extension:jpg.png.pdf.txt.doc.docx.xls.xlsx.ppt.pptx.mp4.rar.zip}'.split('\\.')}")
    private String[] ALLOW_FILE_EXTENSIONS;

    /**
     * 自定义上传目录
     * file.update-path 自定义目录
     * 默认路径为：D://upload
     */
    @Value("${qucheng.upload.path:D://upload}")
    private String UPLOAD_PATH;

    /**
     * 默认上传文件目录
     */
    private static final String DEFAULT_UPLOAD_FOLDER = "temp";

    /**
     * 获取指定ID的文件信息，不包含内容
     *
     * @param id:文件id
     * @return FileInfoDto
     * @author QcCheng Tech
     * @date 2023/10/18
     */
    @RequestMapping(value = "/get", method = {RequestMethod.GET})
    public FileInfoDto getByParam(@RequestParam int id) {
        return fileService.getFile(id);
    }


    /**
     * 获取指定ID的文件信息，不包含内容
     *
     * @param id:文件id
     * @return FileInfoDto
     * @author QcCheng Tech
     * @date 2023/10/18
     */
    @RequestMapping(value = "/get/{id}", method = {RequestMethod.GET})
    public FileInfoDto getByPath(@PathVariable(required = true) int id) {
        return fileService.getFile(id);
    }

    /**
     * 文件上传接口，可同时传入文件、文件所属系统和模块信息
     * 需要指定表单类型为multipart/form-data
     * <p>
     * 文件上传处理流程
     * 1.验证文件信息：非空、是否是允许的文件扩展名
     * 2.将文件接收到指定的system/module目录(如未指定放到用户当前工作目录下的temp目录)下，文件名命名为UUID+扩展名
     * 3.将文件信息保存到数据库表中，产生1个顺序编号的文件ID返回
     *
     * @param dto 上传对象
     * @return qc.module.platform.dto.file.FileUploadResultDto
     * @author QuCheng Tech
     * @since 2023/6/8
     */
    @RequestMapping(value = "/upload", method = {RequestMethod.POST}, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
    public FileUploadResultDto uploadFile(@ModelAttribute FileUploadInputDto dto) throws QCPromptException {
        FileUploadResultDto result = new FileUploadResultDto();
        result.setSuccess(false);

        if (dto == null) {
            result.setMsg("上传输入的信息为空");
            return result;
        }

        if (dto.getFile().isEmpty()) {
            result.setMsg("上传的文件为空");
            return result;
        }

        //System.out.println("getName=" + file.getName() + " getOriginalFilename=" + file.getOriginalFilename());
        //getName=file getOriginalFilename=国能阳宗海发电有限公司门禁系统信息智能化改造采购文件（商务）.docx
        //获取上传的原始文件名
        String originalFileName = dto.getFile().getOriginalFilename();
        //去除最后的点和扩展名
        if (!StringUtils.isBlank(originalFileName)) {
            int dotIndex = originalFileName.lastIndexOf('.');
            if (dotIndex > 0x0)
                originalFileName = originalFileName.substring(0, dotIndex);
        }
        result.setFilename(originalFileName);

        String fileExtension = getExtension(dto.getFile());
        if (!isValidExtension(fileExtension)) {
            result.setMsg("文件类型错误，不被允许上传的文件扩展名");
            return result;
        }
        result.setExtension(fileExtension);

        //获取文件大小，原始单位B，转换为MB
        float fileSize = dto.getFile().getSize() / 1024f / 1024f;

        //写入成功后返回保存到服务器上的文件名信息,为System和Module赋默认值
        if (StringUtils.isBlank(dto.getSystem()))
            dto.setSystem(DEFAULT_UPLOAD_FOLDER);
        if (StringUtils.isBlank(dto.getModule()))
            dto.setModule(DEFAULT_UPLOAD_FOLDER);

        //判断文件的存在目录，根据输入的system和module信息来，如果没有输入使用默认
        //上传文件的存储路径为：上传路径\system\module
        String destFilePath = UPLOAD_PATH + QCConstant.ROUTE_SPLIT + dto.getSystem() + QCConstant.ROUTE_SPLIT + dto.getModule();
        //判断目标目录是否存在
        File file = new File(destFilePath);
        if (!file.exists() || !file.isDirectory()) {
            file.mkdirs();
        }
        //本地测试时目录不存在会自动创建，实际环境暂时不清楚
        //生成UUID作为文件名，避免文件重复
        String uuidFileName = IdentifierUtil.randomUUID();
        //目标文件完整名称
        String destFileFullPath = destFilePath + QCConstant.ROUTE_SPLIT + uuidFileName + "." + fileExtension;

        try (InputStream inputStream = dto.getFile().getInputStream();
             FileOutputStream outputStream = new FileOutputStream(new File(destFileFullPath))) {
            //使用IOUtils包中提供的方法
            IOUtils.copy(inputStream, outputStream);

            //文件信息保存到数据库表，表中有文件ID，返回文件ID
            int fileId = fileService.add(dto.title, originalFileName, uuidFileName, dto.getSystem(), dto.getModule(), destFilePath, fileExtension, fileSize);

            result.setSuccess(true);
            result.setFileid(fileId);
        } catch (IOException ex) {
            result.setMsg("创建文件异常，" + ex.getMessage());
        }

        return result;
    }

    @GetMapping(value = "/image", produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_PNG_VALUE})
    @ResponseBody
    public byte[] getImageByParam(@RequestParam int id) throws IOException, QCPromptException {
        return getFileBytes(id);
    }

    @GetMapping(value = "/image/{id}", produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_PNG_VALUE})
    @ResponseBody
    public byte[] getImageByPath(@PathVariable(required = true) int id) throws IOException, QCPromptException {
        return getFileBytes(id);
    }

    @GetMapping(value = "/pdf", produces = {MediaType.APPLICATION_PDF_VALUE})
    @ResponseBody
    public byte[] getPdfByParam(@RequestParam int id) throws IOException, QCPromptException {
        return getFileBytes(id);
    }

    @GetMapping(value = "/pdf/{id}", produces = {MediaType.APPLICATION_PDF_VALUE})
    @ResponseBody
    public byte[] getPdfByPath(@PathVariable(required = true) int id) throws IOException, QCPromptException {
        return getFileBytes(id);
    }

    @GetMapping(value = "/view", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    @ResponseBody
    public void viewFileByParam(@RequestParam int id, HttpServletResponse response) throws IOException, QCPromptException {
        getFile(id, true, response);
    }

    @GetMapping(value = "/view/{id}", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    @ResponseBody
    public void viewFileByPath(@PathVariable(required = true) int id, HttpServletResponse response) throws IOException, QCPromptException {
        getFile(id, true, response);
    }

    @GetMapping(value = "/download", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    @ResponseBody
    public void downloadFileByParam(@RequestParam int id, HttpServletResponse response) throws IOException, QCPromptException {
        getFile(id, false, response);
    }

    @GetMapping(value = "/download/{id}", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE})
    @ResponseBody
    public void downloadFileByPath(@PathVariable(required = true) int id, HttpServletResponse response) throws IOException, QCPromptException {
        getFile(id, false, response);
    }

    /**
     * 获取文件，进行文件下载；查看和下载本质处理没有差异，只是后台进行区分操作用于记录查看和下载次数
     * <p>
     * 参考：SpringBoot实现文件下载功能的方式分享  https://www.jb51.net/article/278788.htm
     *
     * @param fileId   文件ID
     * @param isView   是进行查看还是下载，true为查看，false为下载
     * @param response 对应请求的响应，在响应中输出文件内容
     * @return void
     * @author QuCheng Tech
     * @since 2023/7/17
     */
    void getFile(int fileId, boolean isView, HttpServletResponse response) throws IOException, QCPromptException {
        //从数据库中读取文件信息
        QcFiles fileInfo = fileService.get(fileId);
        String fileShortName = fileInfo.getTitle() + "." + fileInfo.getExtension();
        String fileFullPath = fileInfo.getPath() + QCConstant.ROUTE_SPLIT + fileInfo.getName() + "." + fileInfo.getExtension();

        //更新文件查看/下载次数
        if (isView) {
            fileService.view(fileId);
        } else {
            fileService.download(fileId);
        }

        //文件头信息从文件中读取并进行设置
        String fileType = new MimetypesFileTypeMap().getContentType(fileFullPath);
        System.out.println("fileType=" + fileType);
        // 设置contenttype，即告诉客户端所发送的数据属于什么类型
        response.setHeader("Content-type", fileType);
        // 设置显示下载的文件名,设置字符集是避免文件名中有中文时出现乱码
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileShortName.getBytes(), StandardCharsets.ISO_8859_1));
        response.addHeader("Pargam", "no-cache");
        response.addHeader("Cache-Control", "no-cache");

        File file = new File(fileFullPath);
        long fileLength = file.length();
        response.addHeader("Content-Length", String.valueOf(fileLength));

        // 将文件写入输入流
        try (FileInputStream fileInputStream = new FileInputStream(file);
             BufferedInputStream inputStream = new BufferedInputStream(fileInputStream)) {
            //byte[] buffer = new byte[inputStream.available()];
            //inputStream.read(buffer);

            //try (ServletOutputStream outputStream = response.getOutputStream()) {
            //    outputStream.write(buffer);
            //    outputStream.flush();
            //
            //    //使用=null赋值，内存也不释放
            //    buffer = null;
            //
            //    response.flushBuffer();
            //} catch (OutOfMemoryError e) {
            //    e.printStackTrace();
            //}

            //修改为使用分块读，每块读完后写入
            try (ServletOutputStream outputStream = response.getOutputStream()) {
                byte[] buffer = new byte[1024];
                int len = 0;
                while ((len = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }
                outputStream.flush();
            } catch (OutOfMemoryError e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取指定ID的文件bytes
     *
     * @param fileId 文件ID
     * @return byte[]
     * @author QuCheng Tech
     * @since 2023/7/17
     */
    byte[] getFileBytes(int fileId) throws IOException, QCPromptException {
        //从数据库中读取文件信息
        QcFiles fileInfo = fileService.get(fileId);
        String fileFullPath = fileInfo.getPath() + QCConstant.ROUTE_SPLIT + fileInfo.getName() + "." + fileInfo.getExtension();

        File file = new File(fileFullPath);
        FileInputStream fileInputStream = new FileInputStream(fileFullPath);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
        try {
            byte[] bytes = new byte[bufferedInputStream.available()];
            bufferedInputStream.read(bytes, 0, bufferedInputStream.available());

            return bytes;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            bufferedInputStream.close();
            fileInputStream.close();
        }

        return null;
    }

    /**
     * 根据指定的路径和文件名获取file对象，不存在路径或文件时进行创建
     */
    private File getFile(String filePath, String filename) throws IOException {
        File file = new File(filePath + QCConstant.ROUTE_SPLIT + filename);
        System.out.println("file:" + filePath + QCConstant.ROUTE_SPLIT + filename);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        if (!file.exists()) {
            file.createNewFile();
        }
        return file;
    }

    /**
     * 验证文件类型是否正确
     */
    private boolean isValidExtension(String extension) {
        for (String allowedExtension : ALLOW_FILE_EXTENSIONS) {
            if (extension.equalsIgnoreCase(allowedExtension)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取扩展名，不包含点
     */
    private String getExtension(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        return originalFilename.substring(originalFilename.lastIndexOf('.') + 1);
    }

    /***
     * 查询文件
     *
     * @param condition 查询条件
     * @return java.util.List<qc.module.platform.dto.file.FileInfoDto>
     * @author QuCheng Tech
     * @since 2023/8/14
     */
    @RequestMapping(value = "/query", method = {RequestMethod.POST, RequestMethod.GET})
    public List<FileInfoDto> query(@RequestBody FileQueryConditionDto condition) throws QCPromptException {
        return fileService.query(condition);
    }
}
